/** @file    User.cpp
 *  @author  Alessandro Polo
 *  @version $Id: User.cpp 2832 2010-08-02 22:53:46Z alex $
 *  @brief
 * File containing methods for the wosh::User class.
 * The header for this class can be found in User.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <core/User.h>
 #include <core/ObjectAllocator.h>
 #include <core/UserManager.h>
 #include <core/Utilities.h>
 #include <core/MethodsCommon.h>
 #include <core/DataModels.h>


using namespace std;
namespace wosh {

 // register as abstract class, because default contructors are private
 WOSH_REGISTER(wosh::User, "wosh::Object", 1.01, _static_User)

std::string User::root = "root";

void user_assign( User* user, long id ) {
	if ( user == NULL ) return;
	user->id = id;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
 
User::User()
	: Object( UserManager::getInstance() ) {
	clear();
	Object::setName( "User", this->id );
	this->objPermission.setGroup("");
	this->objPermission.setMask( Permission::RW, Permission::Read, Permission::None );
	initUser();
 }

User::User( long Id, const std::string& Name, const std::string& password )
	: Object( UserManager::getInstance() ) {
	clear();
	setName( Name );
	this->objPermission.setGroup("");
	this->objPermission.setMask( Permission::RW, Permission::Read, Permission::None );

	this->id = Id;
	this->pwd = password;
	this->pwd_proto = pwd_proto;

	initUser();
 }

User::User( long Id, const std::string& Name, const std::string& password, const std::string& groupsAsString )
	: Object( UserManager::getInstance() ) {
	clear();
	setName( Name );
	this->objPermission.setGroup("");
	this->objPermission.setMask( Permission::RW, Permission::Read, Permission::None );

	this->id = Id;
	this->pwd = password;

	Utilities::splitString(groupsAsString, ";", this->groups, false, true);
	initUser();
 }

User::User(const User& m)
	: Object(m) {
	clear();
	initUser();
	*this = m;
 }

User::~User() { }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS

void User::initUser() {

	Properties.set( _KEY_id,	this->id, Permission( Permission::Read ) );
	Properties.updateObjectKeys(this);

	// register default methods
	Methods.setPermission( Object::getPermission(), Permission::Overwrite_All, true, true );
	Methods.getListPermission().setMask( Permission::Read, Permission::Read, Permission::Read );

	MethodPropertyT::createMethods( &Methods, &Properties, NULL );

	MethodChT::createMethods( &Methods, this, &Properties, NULL );

//	MethodRequest* mmConnect = Methods.create<MethodRequest>( "asd", "connect", Permission::R_XR_XR__() );
//	mmConnect->setMethod( this, (MethodRequestPtr)&User::mmDoConnect );

}

void User::clear() {
	this->id = Utilities::randomLong();
	this->id = _GET_Abs(this->id);
	setName( Utilities::format("User.Unknown#", this->id) );
	this->pwd = "";
	this->pwd_proto = "plain";
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void User::setName( const std::string& name ) {
	Object::setName(name);
	Properties.transactionBeginWrite();
	Properties.update( _KEY_name, Object::getName() );
	Properties.transactionEnd();
	this->objPermission.setOwner(Object::getName());
 }

void User::setPermission( const Permission& value ) {
	Object::setPermission(value);
	Properties.updateObjectKeys( this );
	Properties.setPermission( Object::getPermission(), Permission::Overwrite_All, true, true );
 }

void User::setPermission( const Permission& value, short overwrite_mode, bool when_empty ) {
	Object::setPermission(value, overwrite_mode, when_empty);
	Properties.updateObjectKeys( this );
	Properties.setPermission( Object::getPermission(), Permission::Overwrite_All, true, true );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void User::updateLocation( const string& location, int64 timestamp, double probability )
 {
	if ( timestamp != 0 )
		Location.updateTimeStampOf(location, timestamp);
	//if ( probability_offset != 0.0 )
	Location.updateProbabilityOf(location, probability, false);
 
	string last_location = "";
	UserInfoLocationData data;
	Location.getLocationLast(last_location, data);
	Properties.set("LocationLast", last_location );
	Properties.set("LocationLastTs", data.timestamp );
 }

void User::updateCommmunicator( const string& communicator, int64 timestamp, double ranking_offset )
 {
	if ( timestamp != 0 )
		Communicators.updateTimeStampOf(communicator, timestamp);
	if ( ranking_offset != 0.0 )
		Communicators.updateRankingOf(communicator, ranking_offset, true);
 
	string last_communicator = "";
	UserInfoCommmunicatorData data;
	Communicators.getCommmunicatorLast(last_communicator, data);
	Properties.set("CommunicatorLast", last_communicator );
	Properties.set("CommunicatorLastTs", data.timestamp );
}

///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS

bool User::updatingProperty( bool& , const Variant& , Property& , const PropertiesProvider* ) {
	return true;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS

User& User::operator=( const User& m ) {
	if (this == &m) return *this; // same object?

	setName( m.getName() );
	this->id = m.id;
	this->pwd = m.pwd;
	this->pwd_proto = m.pwd_proto;
	Properties = m.Properties;
	Location = m.Location;
	Communicators = m.Communicators;

	return *this;
 }

bool User::operator==( const User& other ) const {
	if ( this->id != other.id )	return false;
	if ( this->getName() != other.getName() ) return false;
	return true;
 }

/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool User::isMemberOf( const std::string& group ) const {
	std::vector<string>::const_iterator it;
	for ( it=this->groups.begin(); it!=this->groups.end(); ++it ) {
		if ( *it == group )
			return true;
	 }
	return false;
 }

bool User::isValidPassword( const std::string& password,  const std::string& password_proto ) const {
	if ( this->pwd_proto != password_proto ) return false;
	if ( this->pwd != password ) return false;
	return true;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void User::setAtHome( bool value ) {
	Properties.set(_User_KEY_IsAtHome, value);
 }

bool User::isAtHome() const {
	Variant boolData = Properties.getValueOf(_User_KEY_IsAtHome);
	if ( boolData.isEmpty() )
		return false;
	if ( !boolData.isBoolean() )
		return false;
	return boolData.toBoolean(false);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool User::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = true;
	ret = hash_context->update( this->id ) & ret;
	ret = hash_context->update( this->pwd ) & ret;
	ret = hash_context->update( this->pwd_proto ) & ret;
	ret = this->Properties.updateHash(hash_context) & ret;
	ret = this->Location.updateHash(hash_context) & ret;
	ret = this->Communicators.updateHash(hash_context) & ret;
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

DataFieldCollector<User>* User::getDataFields() {
	DataFieldCollector<User>* fields = new DataFieldCollector<User>();
	fields->add<long, Variant>(&User::getID, "ID", 0);
	fields->add<const std::string&, Variant>(&User::getName, "Name", 1);
	fields->add<const char*, Variant>(&User::getClassName, "Type", 2);

	return fields;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* User::mmDoConnect( const Request* request ) {
	if ( request == NULL ) return NULL;
//	Log(LOG_VERBOSE, ":mmDoConnect() : Calling connect(15000).." );
	WRESULT ret = 0;
	return request->replyResponse(ret);
 }

Response* User::mmDoDisconnect( const Request* request ) {
	if ( request == NULL ) return NULL;
// 	Log(LOG_VERBOSE, ":mmDoDisconnect() : Calling disconnect(15000).." );
	WRESULT ret = 0;
	return request->replyResponse(ret);
 }

/*
	else if ( request->getMethod() == _UserManager_METHOD_listloc ) {
		cUser->getLocation().getLocations().transactionBeginRead();
		Table* tbl = new Table( cUser->getLocation().getLocations().size(), 3, true );
		tbl->setTableName("Locations of " + cUser->getName());
		tbl->setHeaderHorizontal("Location", 0);
		tbl->setHeaderHorizontal("Timestamp", 1);
		tbl->setHeaderHorizontal("Probability", 2);
		unsigned int iRow = 0;
		std::map<string, UserInfoLocationData>::const_iterator it;
		std::map<string, UserInfoLocationData>::const_iterator it_end = cUser->getLocation().getLocations().end();
		for ( it=cUser->getLocation().getLocations().begin(); it!=it_end; ++it, ++iRow ) {
			tbl->set<Variant>( it->first, iRow, 0);
			tbl->set( new Variant(it->second.timestamp, Variant::DATETIME), iRow, 1);
			tbl->set<Variant>( it->second.probability, iRow, 2);
		}
		cUser->getLocation().getLocations().transactionEnd();
		response = new Response( _UserManager_METHOD_listloc, tbl );
	 }
	else if ( request->getMethod() == _UserManager_METHOD_listcomm ) {
		cUser->getCommunicators().getCommmunicators().transactionBeginRead();
		Table* tbl = new Table( cUser->getCommunicators().getCommmunicators().size(), 3, true );
		tbl->setTableName("Communicators of " + cUser->getName());
		tbl->setHeaderHorizontal("Communicator", 0);
		tbl->setHeaderHorizontal("Timestamp", 1);
		tbl->setHeaderHorizontal("Ranking", 2);
		unsigned int iRow = 0;
		std::map<string, UserInfoCommmunicatorData>::const_iterator it;
		std::map<string, UserInfoCommmunicatorData>::const_iterator it_end = cUser->getCommunicators().getCommmunicators().end();
		for ( it=cUser->getCommunicators().getCommmunicators().begin(); it!=it_end; ++it, ++iRow ) {
			tbl->set<Variant>( it->first, iRow, 0);
			tbl->set( new Variant(it->second.timestamp, Variant::DATETIME), iRow, 1);
			tbl->set<Variant>( it->second.ranking, iRow, 2);
		}
		cUser->getCommunicators().getCommmunicators().transactionEnd();
		response = new Response( _UserManager_METHOD_listcomm, tbl );
	 }
	else if ( request->getMethod() == _UserManager_METHOD_rankcomm ) {
		if ( !request->hasData() ) return request->replyResponse(WRET_ERR_PARAM, "Invalid argument [missing]");
		if ( !request->getData()->isKindOf<List>() ) request->replyResponse(WRET_ERR_PARAM, "Invalid argument [not List]");
		Variant dCommunicator;
		Variant dRank;
		request->extractArguments(dCommunicator, dRank);
		if ( !dCommunicator.isStringNotEmpty() || dRank.isEmpty() )
			return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [communicator, rank-value]");
		cUser->getCommunicators().getCommmunicators().transactionBeginRead();

		if ( cUser->getCommunicators().getCommmunicators().exists(dCommunicator.asString()) )
			cUser->getCommunicators().getCommmunicators().find(dCommunicator.asString()).ranking = dRank.toDouble(0.0);
		else {
			UserInfoCommmunicatorData data;
			data.timestamp = Utilities::std_time();
			data.ranking = dRank.toDouble(0.0);
			cUser->getCommunicators().getCommmunicators().set(dCommunicator.asString(), data);
		 }
		cUser->getCommunicators().getCommmunicators().transactionEnd();
		return request->replyResponse(WRET_OK, "Updated!");
	 }
*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* User::getUserPresenceAsString( User::PRESENCE value ) {
	switch(value) {
		case User::PRESENCE_OFFLINE:		return "OFFLINE";
		case User::PRESENCE_OFFLINE_AWAY:	return "OFFLINE_AWAY";
		case User::PRESENCE_ONLINE:			return "ONLINE";
		case User::PRESENCE_ONLINE_AWAY:	return "ONLINE_AWAY";
		case User::PRESENCE_ONLINE_DND:		return "ONLINE_DND";
		case User::PRESENCE_UNKNOWN:
		default:							return "UNKNOWN";
	 }
 }

const char* User::getUserStatusAsString( User::STATUS value ) {
	switch(value) {
		case User::STATUS_LOGGEDIN:			return "LOGGEDIN";
		case User::STATUS_LOGGEDOUT:		return "LOGGEDOUT";
		case User::STATUS_UNKNOWN:
		default:							return "UNKNOWN";
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
